/*
 *  Licensed to the Apache Software Foundation (ASF) under one
 *  or more contributor license agreements.  See the NOTICE file
 *  distributed with this work for additional information
 *  regarding copyright ownership.  The ASF licenses this file
 *  to you under the Apache License, Version 2.0 (the
 *  "License"); you may not use this file except in compliance
 *  with the License.  You may obtain a copy of the License at
 *
 *    http://www.apache.org/licenses/LICENSE-2.0
 *
 *  Unless required by applicable law or agreed to in writing,
 *  software distributed under the License is distributed on an
 *  "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
 *  KIND, either express or implied.  See the License for the
 *  specific language governing permissions and limitations
 *  under the License.
 *
 */
package org.apache.asyncweb.server;

import java.util.Collections;
import java.util.Iterator;
import java.util.List;
import java.util.concurrent.CopyOnWriteArrayList;

import org.apache.asyncweb.server.context.BasicKeepAliveStrategy;
import org.apache.asyncweb.server.context.CounterKeepAliveStrategy;
import org.apache.asyncweb.server.context.KeepAliveStrategy;
import org.apache.asyncweb.server.errorReporting.ErrorResponseFormatter;
import org.apache.asyncweb.server.errorReporting.StandardResponseFormatter;
import org.apache.asyncweb.server.session.DefaultSessionAccessor;
import org.apache.asyncweb.server.session.HttpSessionAccessor;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

/**
 * Basic <code>ServiceContainer</code> implementation.
 * 
 * 
 */
public class BasicServiceContainer implements ServiceContainer {

	private static final Logger LOG = LoggerFactory
			.getLogger(BasicServiceContainer.class);

	/**
	 * The default number of keep-alive requests
	 */
	private static final int DEFAULT_KEEP_ALIVE_REQUESTS = 75;

	/**
	 * Represents no limit on the number of keep-alive requests
	 */
	private static final int INFINITE_KEEP_ALIVES = -1;

	private boolean isStarted;

	private int maxKeepAlives = DEFAULT_KEEP_ALIVE_REQUESTS;

	private KeepAliveStrategy keepAliveStrategy = new CounterKeepAliveStrategy(
			maxKeepAlives);

	private HttpSessionAccessor sessionAccessor;

	private ErrorResponseFormatter errorResponseFormatter = new StandardResponseFormatter();

	private boolean sendServerHeader = true;

	private List<HttpServiceFilter> filters = new CopyOnWriteArrayList<HttpServiceFilter>();

	private List<Transport> transports = new CopyOnWriteArrayList<Transport>();

	public boolean isSendServerHeader() {
		return sendServerHeader;
	}

	public void setSendServerHeader(boolean sendServerHeader) {
		this.sendServerHeader = sendServerHeader;
	}

	public ErrorResponseFormatter getErrorResponseFormatter() {
		return errorResponseFormatter;
	}

	public void setErrorResponseFormatter(
			ErrorResponseFormatter errorResponseFormatter) {
		if (errorResponseFormatter == null) {
			throw new NullPointerException("errorResponseFormatter");
		}
		this.errorResponseFormatter = errorResponseFormatter;
	}

	public int getMaxKeepAlives() {
		return maxKeepAlives;
	}

	/**
	 * Returns the employed {@link KeepAliveStrategy} of this container.
	 */
	public KeepAliveStrategy getKeepAliveStrategy() {
		return keepAliveStrategy;
	}

	/**
	 * Sets the maximum number of keep-alive requests
	 * 
	 * @param maxKeepAlives
	 *            THe maximum number of keep alive requests
	 */
	public void setMaxKeepAlives(int maxKeepAlives) {
		if (maxKeepAlives < INFINITE_KEEP_ALIVES) {
			throw new IllegalArgumentException("Invalid keep alives: "
					+ maxKeepAlives);
		}
		this.maxKeepAlives = maxKeepAlives;
		if (LOG.isInfoEnabled()) {
			if (maxKeepAlives == INFINITE_KEEP_ALIVES) {
				LOG.info("Infinite keep-alives configured");
			} else {
				LOG.info("Max keep-alives configured: " + maxKeepAlives);
			}
		}

		keepAliveStrategy = maxKeepAlives == -1 ? new BasicKeepAliveStrategy()
				: new CounterKeepAliveStrategy(maxKeepAlives);
	}

	/**
	 * Adds a <code>ServiceHandler</code> to this container
	 * 
	 * @param handler
	 *            The handler to add
	 * @throws IllegalStateException
	 *             If this container has been started
	 */
	public void addServiceFilter(HttpServiceFilter handler) {
		if (isStarted) {
			throw new IllegalStateException(
					"Attempt to add filter to running container");
		}
		if (LOG.isInfoEnabled())
			LOG.info("Adding service handler '" + handler + "'");
		filters.add(handler);
	}

	/**
	 * Adds a <code>Transport</code> to this container
	 * 
	 * @param transport
	 *            The transport to add
	 * @throws IllegalStateException
	 *             If this container has been started
	 */
	public void addTransport(Transport transport) {
		if (isStarted) {
			throw new IllegalStateException(
					"Attempt to add transport to running container");
		}
		if (LOG.isInfoEnabled())
			LOG.info("Adding transport '" + transport + "'");
		transport.setServiceContainer(this);
		transports.add(transport);
	}

	public List<HttpServiceFilter> getServiceFilters() {
		return Collections.unmodifiableList(this.filters);
	}

	/**
	 * Sets all <code>ServiceHandler</code>s employed by this container. Any
	 * existing handlers are removed
	 * 
	 * @param filters
	 *            A list of <code>ServiceHandler</code>s
	 * @throws IllegalStateException
	 *             If this container has been started
	 */
	public void setServiceFilters(List<HttpServiceFilter> filters) {
		if (isStarted) {
			throw new IllegalStateException(
					"Attempt to add filter to running container");
		}

		this.filters.clear();
		for (HttpServiceFilter filter : filters) {
			addServiceFilter(filter);
		}
	}

	/**
	 * Sets all <code>Transport</code>s employed by this container. Any existing
	 * transport are removed
	 * 
	 * @param transports
	 *            A list of <code>Transport</code>s
	 * @throws IllegalStateException
	 *             If this container has been started
	 */
	public void setTransports(List<Transport> transports) {
		if (isStarted) {
			throw new IllegalStateException(
					"Attempt to add transport to running container");
		}

		this.transports.clear();

		for (Transport transport : transports) {
			addTransport(transport);
		}
	}

	public HttpSessionAccessor getSessionAccessor() {
		return this.sessionAccessor;
	}

	public void setSessionAccessor(HttpSessionAccessor sessionAccessor) {
		if (sessionAccessor == null) {
			throw new NullPointerException("sessionAccessor");
		}
		this.sessionAccessor = sessionAccessor;
	}

	public void start() throws ContainerLifecycleException {
		if (!isStarted) {
			if (LOG.isDebugEnabled())
				LOG.debug("BasicServiceContainer starting");
			startSessionAccessor();
			startHandlers();
			startTransports();
			if (LOG.isDebugEnabled())
				LOG.debug("BasicServiceContainer started");
			isStarted = true;
		}
	}

	public void stop() {
		if (isStarted) {
			isStarted = false;
			if (LOG.isDebugEnabled())
				LOG.debug("BasicServiceContainer stopping");
			stopHandlers();
			stopTransports();
			stopSessionAccessor();
			if (LOG.isDebugEnabled())
				LOG.debug("BasicServiceContainer stopped");
		}
	}

	/**
	 * Starts our session accessor. If no session accessor has been configured,
	 * a default accessor is employed
	 */
	private void startSessionAccessor() {
		if (sessionAccessor == null) {
			if (LOG.isInfoEnabled())
				LOG.info("No SessionAccessor configured. Using default");
			sessionAccessor = new DefaultSessionAccessor();
		}
		sessionAccessor.init();
	}

	/**
	 * Starts all added handlers
	 */
	private void startHandlers() {
		if (LOG.isDebugEnabled())
			LOG.debug("Starting handlers");
		Iterator<HttpServiceFilter> it = filters.iterator();
		while (it.hasNext()) {
			it.next().start();
		}
		if (LOG.isDebugEnabled())
			LOG.debug("Handlers started");
	}

	private void stopHandlers() {
		if (LOG.isDebugEnabled())
			LOG.debug("Stopping handlers");
		HttpServiceFilter handler;
		Iterator<HttpServiceFilter> it = filters.iterator();
		while (it.hasNext()) {
			handler = it.next();
			if (LOG.isInfoEnabled())
				LOG.info("Stopping handler '" + handler + "'");
			handler.start();
			if (LOG.isInfoEnabled())
				LOG.info("Handler '" + handler + "' stopped");
		}
		if (LOG.isDebugEnabled())
			LOG.debug("Handlers stopped");
	}

	private void stopSessionAccessor() {
		if (LOG.isDebugEnabled())
			LOG.debug("Disposing session accessor");
		sessionAccessor.dispose();
		if (LOG.isDebugEnabled())
			LOG.debug("Session accessor disposed");
	}

	/**
	 * Starts all added transports
	 * 
	 * @throws ContainerLifecycleException
	 *             If we fail to start a transport
	 */
	private void startTransports() throws ContainerLifecycleException {
		if (LOG.isDebugEnabled())
			LOG.debug("Starting transports");
		Transport transport;
		Iterator<Transport> it = transports.iterator();
		while (it.hasNext()) {
			transport = it.next();
			if (LOG.isInfoEnabled())
				LOG.info("Starting transport '" + transport + "'");
			try {
				transport.start();
			} catch (TransportException e) {
				if (LOG.isWarnEnabled())
					LOG.warn("Transport '" + transport + "' failed to start");
				throw new ContainerLifecycleException(
						"Failed to start transport ' " + transport + "'", e);
			}
		}
		if (LOG.isDebugEnabled())
			LOG.debug("Transports started");
	}

	private void stopTransports() {
		if (LOG.isDebugEnabled())
			LOG.debug("Stopping transports");
		boolean isError = false;
		Transport transport;
		Iterator<Transport> it = transports.iterator();
		while (it.hasNext()) {
			transport = it.next();
			if (LOG.isInfoEnabled())
				LOG.info("Stopping transport '" + transport + "'");
			try {
				transport.stop();
				if (LOG.isInfoEnabled())
					LOG.info("Transport '" + transport + "' stopped");
			} catch (TransportException e) {
				if (LOG.isWarnEnabled())
					LOG.warn("Failed to stop transport '" + transport + "'", e);
				isError = true;
			}
		}
		String errorString = isError ? " (One or more errors encountered)" : "";
		if (LOG.isDebugEnabled())
			LOG.debug("Transports stopped" + errorString);
	}
}
